/// ===========================================================================
/// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
/// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
/// PURPOSE.
/// ===========================================================================
/// 
/// Project:        MOSS Faceted Search
/// Author:         Leonid Lyublinski (leonidly@microsoft.com)
/// Company:        Microsoft Services
/// Date:           07/04/2008  Version:        2.5
///
/// ===========================================================================

using System;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml.Serialization;
using System.Data;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Search.Query;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using EntLib = Microsoft.Practices.EnterpriseLibrary;

namespace Microsoft.SharePoint.Portal.ExtendedSearch.Common
{
    /// <summary>
    /// The structure holds search parameters, parsed from querystring and intended to build a querystring. 
    /// </summary>
    /// <example>Search string example: chicken Product:"Bisquick" Method:"bake"</example>
    public struct SearchQuery : IEmpty
    {
        private FormData formData;

        public SearchQuery(string xml):this()
        {
            if (!string.IsNullOrEmpty(xml))
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                XmlElement el = doc.DocumentElement; // assuming <SearchQuery />
                if (el != null && el.HasChildNodes)
                {
                    foreach (XmlElement child in el.ChildNodes)
                    {
                        if (child.Name == "Keywords") Keywords = child.InnerXml;
                        if (child.Name == "View") View = child.InnerXml;
                        if (child.Name == "Scope") Scope = child.InnerXml;
                        if (child.Name == "Start") Start = int.Parse(child.InnerXml);
                        if (child.Name == "IsAdvancedSearch") IsAdvancedSearch = bool.Parse(child.InnerXml);
                        if (child.Name == "QueryMethod") QueryMethod = (QueryMethodEnum)Enum.Parse(typeof(QueryMethodEnum), child.InnerXml);
                        if (child.Name == "Properties")
                        {
                            NameValueCollection properties = new NameValueCollection();
                            if (child.HasChildNodes)
                            {
                                for (int i = 0; i < child.ChildNodes.Count; i++)
                                {
                                    XmlElement property = (XmlElement)child.ChildNodes[i];
                                    properties.Add(property.Attributes["name"].Value, property.InnerXml);
                                }
                                Properties = properties;
                            }
                        }
                    }
                    
                }
            }
        }

        #region Properties
        /// <summary>
        /// Referes to the extract from keyword phrase. "k" in Url
        /// </summary>
        public string Keywords;
        /// <summary>
        /// Refers to the start page, "start1" in url
        /// </summary>
        public int Start;
        /// <summary>
        /// Refers to the view. "v" in Url
        /// </summary>
        public string View;
        /// <summary>
        /// Refers to the scope. "scope" in Url
        /// </summary>
        public string Scope;
        /// <summary>
        /// Referes to the extract from keyword phrase related to the META property. [facet name]:"[facet value]"
        /// </summary>
        public NameValueCollection Properties;
        /// <summary>
        /// Optional data from advanced search
        /// </summary>
        public FormData FormData
        {
            get { return formData; }
            set
            {
                formData = value;
                SyncQuery();
            }
        }
        public bool IsAdvancedSearch;

        public QueryMethodEnum QueryMethod;
        #endregion

        #region Methods
        /// <summary>
        /// Returns a shallow copy of the struct
        /// </summary>
        /// <returns></returns>
        public SearchQuery Clone()
        {
            SearchQuery query = this;
            if (Properties != null)
                query.Properties = new NameValueCollection(Properties);
            return query;
        }

        /// <summary>
        /// Fills Query from FormData
        /// </summary>
        internal void SyncQuery()
        {
            if (!IsEmpty() && !formData.IsEmpty())
            {
                // sync keywords
                Keywords = formData.Keywords.GetKeywords();

                // sync properties that have equal or like syntax
                foreach (AdvancedProp prop in formData.Properties)
                {
                    if (prop.IsEmpty()) continue;
                    if (prop.PropOperator.ID == 1 || prop.PropOperator.ID == 7)
                    {
                        if (Properties[prop.PropName] == null)
                            Properties.Add(prop.PropName, prop.PropValue);
                        else
                            Properties[prop.PropName] += Splitter.Delimiter + prop.PropValue;
                    }
                }

                // sync scope
                string scopes = formData.GetScopesDelimited();
                if (!string.IsNullOrEmpty(scopes)) Properties.Add("Scope", scopes);

                // sync languages
                string languages = formData.GetLanguagesDelimited();
                if (!string.IsNullOrEmpty(languages)) Properties.Add("DETECTEDLANGUAGE", languages);
            }
        }

        /// <summary>
        /// Fills FormData from Query
        /// </summary>
        internal void SyncFormData()
        {
            if (!IsEmpty() && formData.IsEmpty())
            {
                // sync keywords. Simple implementation, consider refining
                if (Keywords.StartsWith("\"") && Keywords.EndsWith("\""))
                {
                    formData.Keywords.PhraseKeywords = Keywords;
                    formData.Keywords.PhraseKeywords.Trim(' ');
                }

                string[] keywords = this.Keywords.Split(' ');
                foreach (string kw in keywords)
                {
                    // do keyword assignment
                    if (kw.StartsWith("+"))
                    {
                        formData.Keywords.AndKeywords += " " + kw.TrimStart('+');
                        formData.Keywords.AndKeywords.Trim(' ');
                    }
                    else if (kw.StartsWith("-"))
                    {
                        formData.Keywords.NotKeywords += " " + kw.TrimStart('-');
                        formData.Keywords.NotKeywords.Trim(' ');
                    }
                    else
                    {
                        formData.Keywords.OrKeywords += " " + kw;
                        formData.Keywords.OrKeywords.Trim(' ');
                    }
                }
                

                // sync scope
                if (!string.IsNullOrEmpty(this.Scope)) formData.Scopes.SetValue(Scope, 1);

            }
        }

        public XmlDocument GetXml()
        {
            XmlDocument doc = new XmlDocument();
            XmlElement query = doc.CreateElement("SearchQuery");
            if (!string.IsNullOrEmpty(Keywords))
            {
                XmlElement el = doc.CreateElement("Keywords");
                el.InnerXml = Keywords;
                query.AppendChild(el);
            }
            {
                XmlElement el = doc.CreateElement("Start");
                el.InnerXml = Start.ToString();
                query.AppendChild(el);
            }
            if (!string.IsNullOrEmpty(View))
            {
                XmlElement el = doc.CreateElement("View");
                el.InnerXml = View;
                query.AppendChild(el);
            }
            if (!string.IsNullOrEmpty(Scope))
            {
                XmlElement el = doc.CreateElement("Scope");
                el.InnerXml = Scope;
                query.AppendChild(el);
            }
            {
                XmlElement el = doc.CreateElement("IsAdvancedSearch");
                el.InnerXml = IsAdvancedSearch.ToString();
                query.AppendChild(el);
            }
            {
                XmlElement el = doc.CreateElement("QueryMethod");
                el.InnerXml = QueryMethod.ToString();
                query.AppendChild(el);
            }
            {
                XmlElement el = doc.CreateElement("Properties");
                if (Properties != null)
                {
                    foreach (string key in Properties)
                    {
                        XmlElement prop = doc.CreateElement("Property");
                        prop.Attributes.Append(doc.CreateAttribute("name"));
                        prop.Attributes["name"].Value = key;
                        prop.InnerXml = Properties[key];
                        el.AppendChild(prop);
                    }
                    query.AppendChild(el);
                }
            }
            {
                XmlElement el = formData.GetXml(doc);
                query.AppendChild(el);
            }
            doc.AppendChild(query);
            return doc;
        }
        #endregion

        #region IEmpty Members
        public bool IsEmpty()
        {
            bool isEmpty = (string.IsNullOrEmpty(Keywords) && ((Properties == null) || !Properties.HasKeys()) && formData.IsEmpty());
            return isEmpty;
        }
        #endregion
    }

    /// <summary>
    /// The structure holds search parameters, parsed from POST request . 
    /// </summary>
    public struct FormData : IEmpty
    {
        #region Properties
        public AdvancedKeywords Keywords;
        public Scope[] Scopes;
        public int[] Languages;
        public string ResultType;
        public bool IsAnd;
        public AdvancedProp[] Properties;
        #endregion

        #region CTOR
        public FormData(int length)
        {
            Keywords = new AdvancedKeywords();
            Languages = new int[100];
            Properties = new AdvancedProp[length];
            Scopes = new Scope[length];
            IsAnd = true;
            ResultType = string.Empty;
        }

        public FormData(int length, string xml):this(length)
        {
            if (!string.IsNullOrEmpty(xml))
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                XmlElement el = doc.DocumentElement; // assuming <FormData />
                if (el != null)
                {
                    foreach (XmlElement child in el.ChildNodes)
                    {
                        if (child.Name == "ResultType") ResultType = child.InnerXml;
                        if (child.Name == "IsAnd") IsAnd = bool.Parse(child.InnerXml);
                        if (child.Name == "Keywords")
                        {
                            if (child.HasChildNodes)
                            {
                                for (int i = 0; i < child.ChildNodes.Count; i++)
                                {
                                    if (child.Name == "AndKeywords") Keywords.AndKeywords = child.InnerXml;
                                    if (child.Name == "NotKeywords") Keywords.NotKeywords = child.InnerXml;
                                    if (child.Name == "OrKeywords") Keywords.OrKeywords = child.InnerXml;
                                    if (child.Name == "PhraseKeywords") Keywords.PhraseKeywords = child.InnerXml;
                                }
                            }
                        }
                        if (child.Name == "Languages")
                        {
                            if (child.HasChildNodes)
                            {
                                int[] languages = new int[child.ChildNodes.Count];
                                for (int i = 0; i < child.ChildNodes.Count; i++)
                                {
                                    languages[i] = int.Parse(child.ChildNodes[i].InnerXml);
                                }
                                Languages = languages;
                            }
                        }
                        if (child.Name == "Scopes")
                        {
                            if (child.HasChildNodes)
                            {
                                Scope[] scopes = new Scope[length];
                                for (int i = 0; i < child.ChildNodes.Count; i++)
                                {
                                    int scopeId = int.Parse(child.ChildNodes[i].InnerXml);
                                    scopes[i] = new Scope(scopeId); 
                                }
                                Scopes = scopes;
                            }                             
                        }
                        if (child.Name == "AdvancedProperties")
                        {
                            if (child.HasChildNodes)
                            {
                                AdvancedProp[] properties = new AdvancedProp[length];
                                for (int i = 0; i < child.ChildNodes.Count; i++)
                                {
                                    string xmlProp = child.ChildNodes[i].OuterXml;
                                    AdvancedProp prop = new AdvancedProp(xmlProp);
                                    properties[i] = prop;
                                }
                                Properties = properties;
                            }                             
                        }
                    }                                      
                }
            }
        }
        #endregion

        #region Methods

        /// <summary>
        /// Returns part of Sql statement responsible for properties
        /// </summary>
        /// <returns></returns>
        public string GetPropsFreetext()
        {
            if (Properties == null) return null;
            ArrayList alProps = new ArrayList();
            foreach (AdvancedProp prop in Properties)
            {
                if (!prop.IsEmpty())
                    alProps.Add(prop.GetPropertySql());
            }
            if (alProps.Count == 0) return null;
            string separator = IsAnd ? " AND " : " OR ";
            string result = Splitter.Join(alProps, separator);
            return result;
        }

        /// <summary>
        /// Returns part of Sql statement responsible for scopes
        /// </summary>
        /// <returns></returns>
        public string GetScopesSql()
        {
            ArrayList alTmp = new ArrayList();
            if (Scopes == null) return null;
            foreach (Scope scope in Scopes)
            {
                if (scope.IsEmpty()) continue;
                alTmp.Add(string.Format("\"scope\" = '{0}'", scope.Name));
            }
            string result = Splitter.Join(alTmp, " OR ");
            return result;
        }

        /// <summary>
        /// Returns delimited string of Scopes
        /// </summary>
        /// <returns></returns>
        public string GetScopesDelimited()
        {
            ArrayList alTmp = new ArrayList();
            foreach (Scope scope in Scopes)
            {
                if (scope.IsEmpty()) continue;
                //if (scope.ID == 0) return null;
                alTmp.Add(scope.Name);
            }
            string result = Splitter.Join(alTmp, Splitter.Delimiter);
            return result;
        }

        /// <summary>
        /// Returns part of Sql statement responsible for detected languages
        /// </summary>
        /// <returns></returns>
        public string GetLanguagesSql()
        {
            ArrayList alTmp = new ArrayList();
            if (Languages == null) return null;
            foreach (int langId in Languages)
            {
                if (langId > 0)
                {
                    alTmp.Add("DetectedLanguage = " + langId);
                }
            }
            string result = Splitter.Join(alTmp, " OR ");
            return result;
        }

        /// <summary>
        /// Returns delimited string of language IDs
        /// </summary>
        /// <returns></returns>
        public string GetLanguagesDelimited()
        {
            string[] tmp = Splitter.ConvertToStringArray(Languages);
            ArrayList alTmp = new ArrayList(tmp);
            alTmp.Remove("9");
            int zero = alTmp.IndexOf("0");
            alTmp.RemoveRange(zero, alTmp.Count - zero);
            if (alTmp.Count > 0)
            {
                return Splitter.Join(alTmp, Splitter.Delimiter);
            }
            return null;
        }

        private string ConvertIntToString(int i) { return i.ToString(); }
        /// <summary>
        /// Check if FormData has any data
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            bool isEmpty = Keywords.IsEmpty() && string.IsNullOrEmpty(GetPropsFreetext());
            return isEmpty;
        }

        public XmlElement GetXml(XmlDocument doc)
        {
            XmlElement formData = doc.CreateElement("FormData");
            if (!string.IsNullOrEmpty(ResultType))
            {
                XmlElement el = doc.CreateElement("ResultType");
                el.InnerXml = ResultType;
                formData.AppendChild(el);
            }
            {
                XmlElement el = doc.CreateElement("IsAnd");
                el.InnerXml = IsAnd.ToString();
                formData.AppendChild(el);
            }
            if (!Keywords.IsEmpty())
            {
                XmlElement el = doc.CreateElement("Keywords");
                if (!string.IsNullOrEmpty(Keywords.AndKeywords)) 
                {
                    XmlElement k = doc.CreateElement("AndKeywords");
                    k.InnerXml = Keywords.AndKeywords;
                    el.AppendChild(k);
                }
                if (!string.IsNullOrEmpty(Keywords.NotKeywords))
                {
                    XmlElement k = doc.CreateElement("NotKeywords");
                    k.InnerXml = Keywords.NotKeywords;
                    el.AppendChild(k);
                }
                if (!string.IsNullOrEmpty(Keywords.OrKeywords))
                {
                    XmlElement k = doc.CreateElement("OrKeywords");
                    k.InnerXml = Keywords.OrKeywords;
                    el.AppendChild(k);
                }
                if (!string.IsNullOrEmpty(Keywords.PhraseKeywords))
                {
                    XmlElement k = doc.CreateElement("PhraseKeywords");
                    k.InnerXml = Keywords.PhraseKeywords;
                    el.AppendChild(k);
                }
                formData.AppendChild(el);
            }
            {
                if (Languages != null)
                {
                    XmlElement el = doc.CreateElement("Languages");
                    for (int i = 0; i < Languages.Length; i++)
                    {
                        XmlElement x = doc.CreateElement("Language");
                        x.InnerXml = Languages[i].ToString();
                        el.AppendChild(x);
                    }
                    formData.AppendChild(el);
                }
            }
            {
                if (Scopes != null)
                {
                    XmlElement el = doc.CreateElement("Scopes");
                    for (int i = 0; i < Scopes.Length; i++)
                    {
                        XmlElement x = doc.CreateElement("Scope");
                        x.InnerXml = Scopes[i].ID.ToString();
                        el.AppendChild(x);
                    }
                    formData.AppendChild(el);
                }
            }
            {
                if (Properties != null)
                {
                    XmlElement el = doc.CreateElement("AdvancedProperties");
                    foreach (AdvancedProp prop in Properties)
                    {
                        XmlElement x = prop.GetXml(doc);
                        el.AppendChild(x);
                    }
                    formData.AppendChild(el);
                }
            }

            return formData;
        }
        #endregion
    }

    /// <summary>
    /// Structure holds advanced keywords
    /// </summary>
    public struct AdvancedKeywords : IEmpty
    {
        public string AndKeywords;
        public string PhraseKeywords;
        public string OrKeywords;
        public string NotKeywords;

        /// <summary>
        /// returns string for the Sql search statement
        /// </summary>
        /// <returns></returns>
        internal string GetKeywords()
        {
            Regex re = new Regex("\\s+", RegexOptions.Singleline);
            ArrayList arKeywords = new ArrayList();
            if (!string.IsNullOrEmpty(PhraseKeywords))
            {
                string tmp = PhraseKeywords.Trim('"');
                arKeywords.Add("\"" + tmp + "\"");
            }
            if (!string.IsNullOrEmpty(AndKeywords))
            {
                string tmp = AndKeywords.Trim('"');
                string temp = "+" + re.Replace(tmp, " +");
                string[] arTmp = temp.Split(' ');
                arKeywords.AddRange(arTmp);
            }
            if (!string.IsNullOrEmpty(OrKeywords))
            {
                string tmp = OrKeywords.Trim('"');
                string temp = re.Replace(tmp, " ");
                string[] arTmp = temp.Split(' ');
                arKeywords.AddRange(arTmp);
            }
            if (!string.IsNullOrEmpty(NotKeywords))
            {
                string tmp = NotKeywords.Trim('"');
                string temp = "-" + re.Replace(tmp, " -");
                string[] arTmp = temp.Split(' ');
                arKeywords.AddRange(arTmp);
            }

            string result = Splitter.Join(arKeywords, " ");
            return result;
        }

        public string GetKeywordsFreetext()
        {
            string keywords = GetKeywords();
            return "FREETEXT(defaultproperties, '" + keywords + "')";
        }

        #region IEmpty Members
        public bool IsEmpty()
        {
            bool isEmpty = (string.IsNullOrEmpty(AndKeywords) &&
                string.IsNullOrEmpty(PhraseKeywords) &&
                string.IsNullOrEmpty(OrKeywords) &&
                string.IsNullOrEmpty(NotKeywords));
            return isEmpty;
        }
        #endregion
    }

    /// <summary>
    /// Structure holds advanced properties
    /// </summary>
    public struct AdvancedProp : IEmpty
    {
        #region Properties
        public string PropName;
        public Operator PropOperator;
        public string PropValue;
        public Common.PropTypeEnum PropType; 
        #endregion

        #region CTOR
        /// <summary>
        /// 
        /// </summary>
        /// <param name="xml"></param>
        public AdvancedProp(string xml)
            : this()
        {
            if (!string.IsNullOrEmpty(xml))
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xml);
                XmlElement el = doc.DocumentElement; // assuming <AdvancedProp />
                if (el != null)
                {
                    if (el.Attributes["name"] != null) PropName = el.Attributes["name"].Value;
                    if (el.Attributes["type"] != null) PropType = (PropTypeEnum) Enum.Parse(typeof(PropTypeEnum), el.Attributes["name"].Value);
                    if (el.Attributes["operatorId"] != null)
                    {
                        PropOperator = new Operator(int.Parse(el.Attributes["operatorId"].Value));
                    }
                }
            }
        } 
        #endregion

        #region Methods
        /// <summary>
        /// Builds Sql bit
        /// </summary>
        /// <returns></returns>
        public string GetPropertySql()
        {
            if (PropName == Common.Resource.ADVSEARCH_PROP_DEFAULT) return null;
            string value;
            if (PropType == PropTypeEnum.DateTime)
            {
                value = string.Format("'{0}'", GetDateSql(PropValue));
            }
            else if (PropType == PropTypeEnum.Text)
            {
                if (PropOperator.ID == 1 || PropOperator.ID == 2)
                    value = string.Format("'%{0}%'", PropValue.Trim());
                else
                    value = string.Format("'{0}'", PropValue.Trim());
            }
            else
                value = PropValue.Trim();

            string result = string.Format("{0} {1} {2}", PropName, PropOperator.Sql, value);
            return result;
        }

        private string GetDateSql(string sdate)
        {
            if (string.IsNullOrEmpty(sdate)) sdate = DateTime.MinValue.ToString();
            DateTime date = DateTime.Parse(sdate);
            return String.Format("{0:D4}/{1:D2}/{2:D2} {3:D2}:{4:D2}:{5:D2}", date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second);
        }

        public XmlElement GetXml(XmlDocument doc)
        {
            XmlElement el = doc.CreateElement("AdvancedProp");
            el.Attributes.Append(doc.CreateAttribute("name"));
            el.Attributes["name"].Value = PropName;
            el.Attributes.Append(doc.CreateAttribute("operatorId"));
            el.Attributes["operatorId"].Value = PropOperator.ID.ToString();
            el.Attributes.Append(doc.CreateAttribute("type"));
            el.Attributes["type"].Value = PropType.ToString();
            el.InnerXml = (PropValue != null)? PropValue: string.Empty;

            return el;
        } 
        #endregion

        #region IEmpty Members
        public bool IsEmpty()
        {
            bool isEmpty = (string.IsNullOrEmpty(PropName) || PropName.Equals(Common.Resource.ADVSEARCH_PROP_DEFAULT));
            return isEmpty;
        }
        #endregion
    }

    public struct Scope : IEmpty
    {
        #region Properties
        public string Name;
        public int ID;
        #endregion

        #region CTOR
        public Scope(int id)
        {
            ID = id;
            if (ID == 0) Name = "People";
            else if (ID == 1) Name = "All Sites";
            else
            {
                // would be good to avoid this call
                Name = string.Empty;
                SetScopeName();
            }
        }
        public Scope(int id, string name)
        {
            ID = id;
            Name = name;
        }
        #endregion

        private void SetScopeName()
        {
            foreach (Scope s in Utility.CachedScopes)
            {
                if (s.ID == ID)
                {
                    Name = s.Name;
                    return;
                }
            }
        }

        #region IEmpty Members
        public bool IsEmpty()
        {
            if (string.IsNullOrEmpty(Name)) return true;
            else return false;
        }
        #endregion
    }

    public struct Operator
    {
        #region CTOR
        public Operator(string displayName)
        {
            ID = -1;
            DisplayName = displayName;
            Sql = string.Empty;
            foreach (Operator op in Utility.Operators)
            {
                if (op.DisplayName.Equals(displayName, StringComparison.CurrentCulture))
                {
                    this = op;
                }
            }
        }
        public Operator(int id)
        {
            ID = id;
            DisplayName = string.Empty;
            Sql = string.Empty;
            foreach (Operator op in Utility.Operators)
            {
                if (op.ID == id)
                {
                    this = op;
                }
            }
        }
        public Operator(int id, string displayName, string sql)
        {
            ID = id;
            DisplayName = displayName;
            Sql = sql;
        }
        #endregion

        #region Properties
        public int ID;
        public string DisplayName;
        public string Sql;
        #endregion
    }
}